/*
 * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.generators.wasm

import org.jetbrains.kotlin.wasm.ir.WasmOp
import java.io.File
import java.io.FileWriter

fun main() {
    val targetDir = File("libraries/stdlib/wasm/src/generated/wasm/internal/")
    generateWasmOps(targetDir)
    generateWasmArrays(targetDir)
}

private fun FileWriter.generateStandardWasmInternalHeader() {
    appendLine(File("license/COPYRIGHT_HEADER.txt").readText())
    appendLine()
    appendLine("package kotlin.wasm.internal")
    appendLine()
    appendLine("//")
    appendLine("// NOTE: THIS FILE IS AUTO-GENERATED by the generators/wasm/WasmIntrinsicGenerator.kt")
    appendLine("//")
    appendLine()
}

private fun generateWasmOps(targetDir: File) {
    FileWriter(targetDir.resolve("_WasmOp.kt")).use { writer ->
        writer.generateStandardWasmInternalHeader()
        writer.appendLine(
            """
            @ExcludedFromCodegen
            @Suppress("unused")
            @Target(AnnotationTarget.FUNCTION)
            @Retention(AnnotationRetention.BINARY)
            internal annotation class WasmOp(val name: String) {
                companion object {
            """.trimIndent()
        )
        WasmOp.values().forEach { op ->
            writer.appendLine("        const val $op = \"$op\"")
        }
        writer.appendLine(
            """
                }
            }
            """.trimIndent()
        )
    }
}

private fun generateWasmArrays(targetDir: File) {
    FileWriter(targetDir.resolve("_WasmArrays.kt")).use { writer ->
        writer.generateStandardWasmInternalHeader()

        writer.appendLine(wasmArrayForType("Any", true))
        val types = listOf(
            "Byte",
            "Char",
            "Short",
            "Int",
            "Long",
            "Float",
            "Double"
        )

        types.forEach { primitive ->
            val isPacked = primitive in setOf(
                "Byte",
                "Char",
                "Short",
            )
            val isUnsigned = primitive == "Char"

            writer.appendLine(
                wasmArrayForType(
                    primitive,
                    false, isPacked, isUnsigned
                )
            )
        }
    }
}

private fun wasmArrayForType(
    klass: String,
    isNullable: Boolean,
    isPacked: Boolean = false,
    isUnsigned: Boolean = false,
): String {
    val type = klass + if (isNullable) "?" else ""
    val name = "Wasm${klass}Array"
    val getSuffix = when {
        isPacked && isUnsigned -> "_U"
        isPacked && !isUnsigned -> "_S"
        else -> ""
    }
    return """
            @Suppress("UNUSED_PARAMETER")
            @WasmArrayOf($klass::class, isNullable = $isNullable)
            internal class $name(size: Int) {
                @WasmOp(WasmOp.ARRAY_GET${getSuffix})
                fun get(index: Int): $type =
                    implementedAsIntrinsic
    
                @WasmOp(WasmOp.ARRAY_SET)
                fun set(index: Int, value: $type): Unit =
                    implementedAsIntrinsic
    
                @WasmOp(WasmOp.ARRAY_LEN)
                fun len(): Int =
                    implementedAsIntrinsic
            }
            
            @Suppress("NOTHING_TO_INLINE")
            internal inline fun copyWasmArray(source: $name, destination: $name, sourceIndex: Int, destinationIndex: Int, length: Int) {
                wasm_array_copy<$name>(destination, destinationIndex, source, sourceIndex, length)
            }
    
            internal inline fun $name.fill(size: Int, init: (Int) -> $type) {
                var i = 0
                while (i < size) {
                    set(i, init(i))
                    i++
                }
            }
            
            """.trimIndent()
}
